from plyny.plio.files import File
from web.fetcher.fetcher import Fetcher
import web.fetcher as fetcher
from web import Url

from tempfile import NamedTemporaryFile
import math
from urlparse import urljoin
import os
from StringIO import StringIO


class Image(object):
    def __init__(self, content=None):
        self._data = content
        self._pil_intern = None

    @classmethod
    def from_url(cls, url):
        return PathedImage(url)

    @classmethod
    def from_file(cls, path):
        from plyny.plio.files import Path

        if not isinstance(path, Path):
            path = Path(path)

        return Image.from_url(path.url())

    def estimate_compression(self):
        import base64
        before = len(base64.b64encode(self.content()))
        filename = NamedTemporaryFile(suffix='.jpg')
        self.save(filename.name, True)
        after = File(filename.name).size()
        return float(after) / before

    def faces(self):
        from io.images import faces

        filename = NamedTemporaryFile(suffix='.tif')
        self.save(filename.name, True)

        return faces.get().detect_for_file(filename.name)
    #return faces.get().detect_for_file(self.to_opencv())

    def histogram(self):
        img = self.to_pil()
        try:
            return img.histogram()
        except IndexError:
            return None

    def entropy(self):
        """calculate the entropy of an image"""
        # http://brainacle.com/2010/10/14/calculating-image-
        # entropy-with-python-how-and-why
        histogram = self.histogram()
        if histogram is None:
            return None
        histogram_length = sum(histogram)

        samples_probability = [float(h) / histogram_length for h in histogram]

        return -sum([p * math.log(p, 2) for p in samples_probability if p !=
            0])

    def text(self):
        """ Extract text from image using OCR from tesseract.
        Requires that tesseract be installed. """

        ocr = 'tesseract'
        from core.os2 import which

        if which(ocr) is None:
            raise ImportError('%s not in path' % ocr)

        inpt = NamedTemporaryFile(suffix='.tif')
        self.save(inpt.name, overwrite=True)
        output = NamedTemporaryFile()

        import subprocess
        subprocess.call('%s %s %s' % (ocr, inpt.name, output.name),
                shell=True, stderr=subprocess.PIPE)

        outfile = output.name + '.txt'
        if not os.path.exists(outfile):
            raise IOError('%s failed' % ocr)

        return open(outfile).read()

    def content(self):
        """ Gets image content as bytes. """

        if self._data is None:
            if self._pil_intern is not None:
                filename = NamedTemporaryFile(suffix='.tif')
                self._pil_intern.save(filename.name)
                self._data = filename.read()

        return self._data

    def to_opencv(self):
        from opencv.highgui import cvQueryFrame, cvCreateFileCapture
        #from opencv.cv import cvQueryFrame
        #from opencv.cv import cvCreateFileCapture

        filename = NamedTemporaryFile(suffix='.tif')
        self.save(filename.name, True)
        frame = cvQueryFrame(cvCreateFileCapture(filename.name))
        if not frame:
            raise IOError('Could not convert')

        return frame

    def to_pil(self):
        """ Gets PIL representation of image, for content
        examination/manipulation.  See http://www.pythonware.com/products/pil/
        """

        if self._pil_intern is None:
            from PIL import Image as img
            self._pil_intern = img.open(StringIO(self.content()))

        return self._pil_intern

    @classmethod
    def from_pil(cls, pil_image):
        i = Image()
        i._pil_intern = pil_image
        return i

    def _filter(self, filtername):
        from PIL import ImageFilter
        p = self.to_pil()
        p = p.filter(getattr(ImageFilter, filtername))
        return Image.from_pil(p)

    def edges(self):
        return self._filter('FIND_EDGES')

    def blur(self):
        return self._filter('BLUR')

    def thumbnail(self):
        from PIL import Image as Img

        size = self.size()
        size = 128, size[1] / (size[0] / 128)
        copy = self.to_pil().copy()
        copy.thumbnail(size, Img.ANTIALIAS)
        return Image.from_pil(copy)

    def crop(self, box):
        return Image.from_pil(self.to_pil().crop(box))
        
    def blend(self, other, alpha=0.5):
        from PIL import Image as Img
        return Image.from_pil(Img.blend(self.to_pil(), other.to_pil(), alpha))

    def area(self):
        """ Get area of image. """
        s = self.size()
        if s is not None:
            return s[0] * s[1]
        return None

    def size(self):
        """ Get size of image. """
        try:
            return self.to_pil().size
        except IOError:
            return None

    def save(self, filename, overwrite=False):
        """ Saves image contents to file. """
        if not overwrite and os.path.exists(filename):
            raise ValueError('%s already exists.' % filename)

        self.to_pil().save(filename)


def parse_number(value):
    try:
        v = value.lower()
        if v.endswith('px'):
            v = v[:-2]

        return int(v)
    except (AttributeError, ValueError):
        pass

    return None


class PathedImage(Image):
    def __init__(self, src, content=None):
        """ Construct image from URL. """
        super(PathedImage, self).__init__(content)

        if not isinstance(src, Url):
            src = Url(src)

        self.src = src

    def url(self):
        return self.src

    def content(self):
        d = super(PathedImage, self).content()
        if d is not None:
            return d

        try:
            data = fetcher.fetch(self.url())
        except Fetcher.raises() as e:
            try:
                msg = u'Could not fetch %s, (%s)' % (self.url(), e)
            except UnicodeDecodeError:
                msg = u'Could not fetch'

            raise IOError(msg)

        if data:
            self._data = data.content
        else:
            raise IOError('Could not fetch %s' % self.url())

        return self._data

    def __str__(self):
        return '%s(url=%s)' % (self.__class__.__name__, self.src)

    def __hash__(self):
        return hash(self.src)

    def __cmp__(self, other):
        return cmp(self.src, other.src)

    def save(self, filename=None, overwrite=False):
        if filename is None:
            filename = self.src.path().split('/')[-1]
        super(PathedImage, self).save(filename, overwrite)


class HTMLImage(PathedImage):
    """ HTML variant of image.  Useful for constructing from HTML element."""

    @staticmethod
    def from_dict(d, base_url):
        r = html_attrs(lambda x: d.get(x, None), base_url)
        if r is None:
            return None

        src, alt, title, width, height = r
        i = HTMLImage(src, alt, width, height)
        i.title = title
        return i

    @staticmethod
    def from_element(element):
        """ Create HTMLImage from lxml element. """

        r = html_attrs(element.get)
        if r is None:
            return None

        src, alt, title, width, height = r
        i = HTMLImage(src, alt, width, height)

        i._element = element

        i.title = title

        p = element.getparent()

        if p.tag == 'a':
            i.href = p.get('href')
            i.a_title = p.get('title')

        return i

    def __init__(self, src, alt=None, width=None, height=None):
        super(HTMLImage, self).__init__(src)

        self.alt = alt
        self.width = width
        self.height = height

        self.title = None
        self.href = None
        self.a_title = None
        self._element = None

    def size(self):
        if self.width and self.height:
            return self.width, self.height

        return super(HTMLImage, self).size()

    def element(self):
        """ Return HTML element this was created from. """
        return self._element


def html_attrs(get, base_url=None):
    src = get('src')
    if src is None:
        src = get('lazyload')

    if src is None:
        return None

    if src is not None:
        if base_url:
            src = urljoin(base_url, src)
        src = Url(src)

    alt = get('alt')
    title = get('alt')

    width = height = None

    width = parse_number(get('width'))
    height = parse_number(get('height'))

    return src, alt, title, width, height
